package com.dlkw.easyroom;

import androidx.sqlite.db.SimpleSQLiteQuery;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class QueryBuilder<T> {
    private final BaseDao<T> dao;

    private final List<Property> selection = new ArrayList<>();
    private final List<Condition<T>> conditions = new ArrayList<>();

    private String orderBy;
    private Integer limit;
    private Integer offset;

    private QueryBuilder(BaseDao<T> dao) {
        this.dao = dao;
    }

    static <T> QueryBuilder<T> create(BaseDao<T> dao) {
        return new QueryBuilder<>(dao);
    }

    public QueryBuilder<T> select(Property... properties) {
        if (properties != null) {
            Collections.addAll(selection, properties);
        }
        return this;
    }

    public QueryBuilder<T> where(Property property, Operator operator, Object... values) {
        if (property != null && operator != null) {
            conditions.add(new Condition<>(property, operator, values));
        }
        return this;
    }

    public QueryBuilder<T> orderBy(Property property, boolean asc) {
        if (property != null) {
            this.orderBy = property.getName() + (asc ? " ASC" : " DESC");
        }
        return this;
    }

    public QueryBuilder<T> limit(int limit) {
        this.limit = limit;
        return this;
    }

    public QueryBuilder<T> offset(int offset) {
        this.offset = offset;
        return this;
    }

    public List<T> list() {
        StringBuilder sql = new StringBuilder("SELECT ");
        for (int i = 0; i < selection.size(); i++) {
            sql.append(selection.get(i).getName());
            if (i < selection.size() - 1) {
                sql.append(",");
            }
        }
        sql.append(" FROM ").append(dao.getTableName());

        List<Object> args = new ArrayList<>();

        if (!conditions.isEmpty()) {
            sql.append(" WHERE ");
            for (int i = 0; i < conditions.size(); i++) {
                Condition<T> condition = conditions.get(i);
                sql.append(condition.property.getName())
                        .append(" ")
                        .append(condition.operator.getSql())
                        .append(" ");

                switch (condition.operator) {
                    case IN:
                        sql.append("(");
                        for (int j = 0; j < condition.values.length; j++) {
                            sql.append("?");
                            if (j < condition.values.length - 1) {
                                sql.append(",");
                            }
                            args.add(condition.values[j]);
                        }
                        sql.append(")");
                        break;
                    default:
                        sql.append("?");
                        args.add(condition.values[0]);
                        break;
                }

                if (i < conditions.size() - 1) {
                    sql.append(" AND ");
                }
            }
        }

        if (orderBy != null) {
            sql.append(" ORDER BY ").append(orderBy);
        }

        if (limit != null && limit > 0) {
            sql.append(" LIMIT ").append(limit);
            if (offset != null && offset > 0) {
                sql.append(" OFFSET ").append(offset);
            }
        }

        return dao.query(new SimpleSQLiteQuery(sql.toString(), args.toArray()));
    }

    public T one() {
        List<T> result = limit(1).list();
        return result.isEmpty() ? null : result.get(0);
    }

    public boolean delete() {
        try {
            dao.execute(new SimpleSQLiteQuery("DELETE FROM " + dao.getTableName() + " WHERE " + conditions.get(0).property.getName() + " = ?", new Object[]{conditions.get(0).values[0]}));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    private static class Condition<T> {
        private final Property property;
        private final Operator operator;
        private final Object[] values;

        public Condition(Property property, Operator operator, Object... values) {
            this.property = property;
            this.operator = operator;
            this.values = values;
        }
    }
}

